# 29. 内置方法

# 内置方法

内置方法:都是__名字__来表示的,也有几个相对应称谓:类中的特殊方法、双下方法、魔术方法等

类中的每一个双下方法都有它自己的特殊意义

所有的内置函数都没有需要在外部直接调用的,而是需要其他的内置函数、方法、特殊的语法来触发内置函数


# call

call 跟__init__方法执行方法差不多,不过需要加个括号

如果类有__call__方法,那在外部的触发条件就是:对象() 或 类名()()

class a:
    def __call__(self, *args, **kwargs):
        print("我是一个__call__内置方法")
a1 = a()
a1()
a()()

如果类中有__call__方法,在实例化后使用对象加括号,程序会默认执行类中的__call__方法

还有一种执行方式,类号加二个括号程序也会默认执行类中的__call__方法


源码通常使用方式

class a:
    def __call__(self, *args, **kwargs):
        print("我是一个__call__内置方法")

class b:
    def __init__(self,call):
        self.call = call()
        self.call()
b(a)

这要调用有什么好处,还需要在写一个类来调用


class a:
    def __call__(self, *args, **kwargs):
        print("我是一个__call__内置方法")

class b:
    def __init__(self,call):
        print("在调用__call__方法先要做的事")
        self.call = call()
        self.call()
        print("在调用__call__方法之后要做的事")
b(a)

# len

想要统计对象的数量

如果类有__len__方法,那在外部的触发条件就是:len(对象)

#这种代码执行是报错,因为类中没有__len__方法,又没给他指定那个对象所以会报错
class a:
    def __init__(self,age):
        self.age = age
a1 = a("22")
print(len(a1))


#以下代码是可以执行成功,因为类中有__len__方法
class a:
    def __init__(self,age):
        self.age = age

    def __len__(self):
        return len(self.age)
a1 = a("22")
print(len(a1))


## 通过以上代码的思路,那是不是可以把__len__方法的返回值给写死,答案是可以的,看下面
class a:
    def __init__(self,age):
        self.age = age

    def __len__(self):
        return 1
a1 = a("22")
print(len(a1))


## 如果不想要用__len__方法也可以,就要加对象名
class a:
    def __init__(self,age):
        self.age = age

a1 = a("22")
print(len(a1.age))

# new

说到__new__就要引出单例类跟多例类,不过说之前,要先明确__new__跟__init__的区别

new:构造方法,用于创建空间

init:初始化方法,用于给新创建的空间赋值

多例类:就是一个类中有多个实例化空间存在

单例类:就是一个类中只有一个实例化空间存在

一般没有只有一个实例化空间的存在,一般都是要么没有实例化空间,要么有多个实例化空间

在这里就主要讲怎么写出单例类出来


class a:
    def __new__(cls, *args, **kwargs):
        print("我是一个__new__方法")
        cls.new = object.__new__(cls)
        return cls.new

    def __init__(self):
        print("我是一个__init__方法")

a1 = a()

执行结果:
我是一个__new__方法
我是一个__init__方法

从上面就看出来,new__方法是比__init__先执行的,为什么平时创建__init__方法的时候不需要用__new,因为在python3创建的类都是继承了object类,如果本类没有,就会去父类找


class a:
    def __new__(cls, *args, **kwargs):
        cls.new = object.__new__(cls)
        return cls.new

    def __init__(self):
        pass

a1 = a()
a2 = a()
print(a1)
print(a2)

执行结果:
<__main__.a object at 0x0000028321CCA5F8>
<__main__.a object at 0x0000028321CD0240>

以上的类就是多例类,如何判断出是不是单例类,只需要对比一下实例化的内存地址,是不是相同的就判断出来了


class a:
    __new = None
    def __new__(cls, *args, **kwargs):
        if not a.__new:
            cls.__new = object.__new__(cls)
        return cls.__new

    def __init__(self):
        pass

a1 = a()
a2 = a()
print(a1)
print(a2)

执行结果:
<__main__.a object at 0x000002D2C951A630>
<__main__.a object at 0x000002D2C951A630>

以上的类就是单例类,因为用了一个变量加上判断,如果这个变量不为空就不会执行创建空间的操作,并向__init__中的self参数返回变量原先的值


# str

更改对象执行时的默认值,但是要触发更改后的值,需要一些条件

如果类有__str__方法,那在外部的触发条件就是:print()、str()、格式化输出%s

class a:
    def __str__(self):
        return "姓名:%s | 年龄:%s | 性别:%s " %(self.name,self.age,self.sex)

    def __init__(self,name,age,sex):
        self.name = name
        self.age = age
        self.sex = sex

a1 = a("江凡",22,"男")
a2 = a("江燕",23,"女")
print("=" * 4, "这是通过print()来触发类里面的__str__方法", "=" * 4)
print(a1)
print(a2)
print("=" * 4, "这是通过str()来触发类里面的__str__方法", "=" * 4)
print(str(a1))
print(str(a2))
print("=" * 4, "这是通过格式化输出来触发类里面的__str__方法", "=" * 4)
print("学生信息:%s" %a1)
print("学生信息:%s" %a2)

执行结果:
==== 这是通过print()来触发类里面的__str__方法 ====
姓名:江凡 | 年龄:22 | 性别:男 
姓名:江燕 | 年龄:23 | 性别:女 
==== 这是通过str()来触发类里面的__str__方法 ====
姓名:江凡 | 年龄:22 | 性别:男 
姓名:江燕 | 年龄:23 | 性别:女 
==== 这是通过格式化输出来触发类里面的__str__方法 ====
学生信息:姓名:江凡 | 年龄:22 | 性别:男 
学生信息:姓名:江燕 | 年龄:23 | 性别:女 

通过执行结果,也看出来__str__在外部是通过什么才能触发的


# repr

__repe__这内置方法有点像是备胎的感觉,但是也有独立的触发条件

__repe__是__str__方法的备用方法

如果类有__repe__方法,那在外部的触发条件就是:repr() 、格式化输出%r

## 这是类中有srt内置方法的情况下
class a:
    def __init__(self,name):
        self.name = name

    def __str__(self):
        return "这是str方法中的%s"%self.name

    def __repr__(self):
        return "这是repr方法中的%s"%self.name
a1 = a("aaa")
print(a1)
print(repr(a1))

执行结果:
这是str方法中的aaa
这是repr方法中的aaa


## 这是类中没有srt内置方法的情况下
class a:
    def __init__(self,name):
        self.name = name

    def __repr__(self):
        return "这是repr方法中的%s"%self.name
a1 = a("aaa")
print(a1)
print(repr(a1))

执行结果:
这是repr方法中的aaa
这是repr方法中的aaa

如果类中有repr方法的同时还有str方法,那么str方法的触发条件都会由str方法来执行

如果类中只有repr方法时,str的触发条件都会由repr方法执行

在继承方面聊聊str跟repr的恩怨情仇

## 这是父类中有srt内置方法的情况下
class a:
    def __str__(self):
        return "这是str方法中的%s"%self.name

    def __repr__(self):
        return "这是repr方法中的%s"%self.name

class b(a):
    def __init__(self,name):
        self.name = name
b1 = b("aaa")
print(b1)
print(repr(b1))

执行结果:
这是str方法中的aaa
这是repr方法中的aaa


## 这是父类中没有srt内置方法的情况下
class a:
    def __repr__(self):
        return "这是repr方法中的%s"%self.name

class b(a):
    def __init__(self,name):
        self.name = name
b1 = b("aaa")
print(b1)
print(repr(b1))

执行结果:
这是repr方法中的aaa
这是repr方法中的aaa

在子类中使用__str__,先找子类的__str__,没有的话要向上找,只要父类不是object,就执行父类的__str__

但是如果出了object之外的父类都没有__str__方法,就执行子类的__repr__方法,如果子类也没有,

还要向上继续找父类中的__repr__方法.

一直找不到 再执行object类中的__str__方法


# del

del:也称为:析构方法,用于释放Python自带的回收机制无法释放的内存空间

在python中是自带内存回收机制的,但是Python中的回收机制只指定自己产生的内存数据,对于打开的文件资源、网络资源等是无能为力的,比如文件资源,之前文章中打开一个文件的,是不是还需要关闭这个文件的资源,因为文件资源是属于系统级的内存资源,Python自带的回收机制是无法操作回收系统级的内存资源,只能通过手动关闭

如果类有__del__方法,那在外部的触发条件就是:del 要回收的对象 、等程序运行完毕自动执行del方法

## 使用自动执行触发模式,到程序完全执行完毕后,才会触发执行__del__方法
class a:
    def __init__(self,name):
        self.name = open(name)

    def __del__(self):
        self.name.close()

a1 = a("student")
print(a1.name.read())

## 使用del来手动触发__del__方法
class a:
    def __init__(self,name):
        self.name = open(name)

    def __del__(self):
        self.name.close()

a1 = a("student")
print(a1.name.read())
del a1

不管是主动还是被动,这个name对象总会被清理掉,被清理掉就触发__del__方法,触发这个方法就会归还操作系统的文件资源

肯定会有一个问题:为什么不用with open来做,这样子就不用去关闭文件资源了

其实with open也是需要关闭文件资源的,不过他只是设置好,在执行结束后默认会执行close()来关闭文件资源

with open 的容错率是百分之90,是不是觉得够高了,并不是,with open在已知中,就有四个对象不能支持容错,所以以后用到with open 要千万注意


# item系列

item系统跟对象使用[]访问有直接的关系

__setitem__  #增加
__getitem__  #查询
__delitem__  #删除
class a:
    def __getitem__(self, item):
        return getattr(self,item)
    def __setitem__(self, key, value):
        setattr(self,key,value)
    def __delitem__(self, key):
        delattr(self,key)
a1 = a()
a1["1"] = "世界很大"  ## 触发了__setitem__方法,把值分别传递给方法中的key,value中 
print(a1.__dict__)
print(a1["1"])  ## 触发了__getitem__,把值传递给item中
del a1["1"]  ## 触发了__delitem__,把值传递给key中
print(a1.__dict__)

执行结果:
{'1': '世界很大'}
世界很大
{}

为什么要用到反射,因为我们传递过去的key值是字符串格式,目前知识点中,只有反射能够使用字符串来作为变量名和查询

__setitem__方法:

  1. 触发条件:对象["key"] = "value"
  2. 最后出的结束是字典类型的数据

__getitem__方法:

  1. 触发条件:对象["key"] / print(对象["key"])
  2. 跟查询字典类型的数据一样,只是把变量名换成实例化对象名
  3. 注意:查询时是返回方法的返回值,不会打印方法中的其它输出

__delitem__方法:

  1. 触发条件:del 对象["key"]
  2. 也跟删除字典一样
  3. 执行删除操作,python会找到类中的__delitem__方法
  4. 注意:
    1. __delitem__不像__del__方法一样,不会程序结束后默认执行,只能通过del 来触发执行
    2. __delitem__方法中,需要做实际的删除操作,不然也不会被删除

另一种用法

通过传递一个列表,使用item系列能这列表进行改查删操作

这里是想说,item系列不止就只有上面一种用法,还有很多种用法

class a:
    def __init__(self,lst):
        self.lst = lst

    def __getitem__(self, item):
        return self.lst[item]

    def __setitem__(self, key, value):
        self.lst[key] = value

    def __delitem__(self, key):
        self.lst.pop(key)

a1 = a(["江","凡","世","界"])
print(a1[:])
a1[1] = "江"
print(a1[:])
del a1[1]
print(a1[:])

执行结果:
['江', '凡', '世', '界']
['江', '江', '世', '界']
['江', '世', '界']

在内置的模块中:有一些特殊的方法,要求对象必须实现__getitem__/__setitem__才能使用


# eq

__eq__方法:对比二个值是否相同

如果类有__eq__方法,那在外部的触发条件就是:对象 == 对象 / 或 如果使用类有用到 值==值 判断就自动触发(当字典或集合进行hash算法后,hash值一样,就会进行第二轮判断 值 == 值 判断)

class a:
    def __init__(self,name,sex):
        self.name = name
        self.sex = sex

    def __eq__(self, other):
        if self.name == other.name and self.sex == other.sex:
            return True
        else:
            return False

a1 = a("江凡",22)
a2 = a("李城",18)
a3 = a("江凡",22)
print(a1 == a2)
print(a1 == a3)

执行结果:
False
True

# hash

hash算法:

  1. 底层数据结构基于hash算法来寻找内存地址的优化操作
  2. 将把某一个值存储在内存中,就会经过hash算法的一系列运算,保证不同值的hash值是不一致的、
  3. 对同一个值每执行一次代码,hash值都会进行变化
  4. 对同一个值,执行一次代码中有多次同值的,hash值不会发生变化

字典的存值:

字典的存值也是使用hash算法来运算的

  1. 先把key值用hash算法运算一下存储位置,key值存储的就是value值的内存地址
  2. 如果在字典中有相同的key值,按Python的执行顺序,最后的值为准

集合的存值

集合的去重是怎么实现的

将集合中的每个值一一取出,就像是用for循环一样,在将每个值进行bash运算,在内存中,集合实际都是存储一大堆运算好的hash值,如果二个值相等就会被覆盖

如果类有__hash__方法,那在外部的触发条件,有使用到字典或集合类型的时候自动调用类中的__hash__方法

class a:
    def __init__(self,name,sex):
        self.name = name
        self.sex = sex

    def __hash__(self):
        return hash('%s%s' %(self.name,self.sex))

    def __eq__(self, other):
        if self.name == other.name and self.sex == other.sex:
            return True

so=[]
for i in range(10):
    so.append(a("江凡",18))
for i in range(10):
    so.append(a("李城",18))
wo = set(so)
for i in wo:
    print(i.__dict__)
    
执行结果:
{'name': '李城', 'sex': 18}
{'name': '江凡', 'sex': 18}